#include "Compiler.h"
#include <boost/format.hpp>
#include "VccException.h"
#include "ExpectedTokenException.h"
#include <iostream>

Compiler::Compiler(char* script_raw_data) {
    this->script.assign_raw_data(script_raw_data);
}

Compiler::~Compiler() {
    // empty
}

bool Compiler::at_cpp_comment() {
    char ch = this->script.get_current_char();
    if (ch == '/') {
        this->script.move_offset(+1);
        ch = this->script.get_current_char();
        this->script.move_offset(-1);
        if (ch == '/') {
            return true;
        }
    }
    return false;
}

void Compiler::skip_cpp_comment() {
    assert(this->at_cpp_comment());
    this->script.move_offset(+2);
    while (!this->at_end() && this->script.get_current_char() != '\n') {
        this->script.move_offset(+1);
    }
    if (!this->at_end()) {
        this->script.move_offset(+1);
    }
}

bool Compiler::at_c_comment() {
    char ch = this->script.get_current_char();
    if (ch == '/') {
        this->script.move_offset(+1);
        ch = this->script.get_current_char();
        this->script.move_offset(-1);
        if (ch == '*') {
            return true;
        }
    }
    return false;
}

bool Compiler::at_c_comment_end() {
    char ch = this->script.get_current_char();
    if (ch == '*') {
        this->script.move_offset(+1);
        ch = this->script.get_current_char();
        this->script.move_offset(-1);
        if (ch == '/') {
            return true;
        }
    }
    return false;
}

void Compiler::skip_c_comment() {
    assert(this->at_c_comment());
    this->script.move_offset(+2);
    while (!this->at_end() && !this->at_c_comment_end()) {
        this->script.move_offset(+1);
    }
    if (!this->at_c_comment_end()) {
        throw ExpectedTokenException("*/", "<EOF>", 0);
    }
    this->script.move_offset(+2);
}

bool Compiler::at_true_whitespace() {
    char ch = this->script.get_current_char();

    return (ch != '\0') && (ch <= ' ');
}

bool Compiler::at_whitespace() {
    return this->at_true_whitespace() || this->at_cpp_comment() || this->at_c_comment();
}

void Compiler::skip_whitespace() {
    while (!this->at_end() && this->at_whitespace()) {
        if (this->at_true_whitespace()) {
            this->script.move_offset(+1);
        } else if (this->at_cpp_comment()) {
            this->skip_cpp_comment();
        } else if (this->at_c_comment()) {
            this->skip_c_comment();
        }
    }
}

bool Compiler::at_end() {
    return !this->script.get_current_char();
}

Token Compiler::get_token() {
    this->skip_whitespace();
    if (this->at_end()) {
        return Token("");
    }

    if (this->at_identifier_initiator()) {
        return this->get_identifier();
    } else if (this->at_number()) {
        return this->get_number();
    } else if (this->at_string_literal()) {
        return this->get_string_literal();
    } else if (this->at_symbol()) {
        return this->get_symbol();
    }

    char ch = this->script.get_current_char();
    throw VccException(str(boost::format("unknown character: %c (%d)") % ch % (int)ch)); 
}

bool Compiler::at_identifier_initiator() {
    char ch = this->script.get_current_char();

    return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch == '_');
}

bool Compiler::at_identifier() {
    return this->at_identifier_initiator();
}

Token Compiler::get_identifier() {
    this->skip_whitespace();

    string text;
    while (this->at_identifier()) {
        text += this->script.get_char();
    }

    return Token(text);
}

unsigned int Compiler::get_parsing_offset() {
    return this->script.get_offset();
}

bool Compiler::at_number() {
    char ch = this->script.get_current_char();

    return (ch >= '0' && ch <= '9'); 
}

IntegerToken Compiler::get_number() {
    this->skip_whitespace();

    string text;
    while (this->at_number()) {
        text += this->script.get_char();
    }

    return IntegerToken(text);
}

bool Compiler::at_symbol() {
    char ch = this->script.get_current_char();

    return (ch == '{' || ch == '}'
        ||  ch == '(' || ch == ')'
        ||  ch == '[' || ch == ']'
        ||  ch == '-' || ch == '+'
        ||  ch == '/' || ch == '*'
        ||  ch == ';' || ch == ':'
        ||  ch == '"' || ch == '\'' 
        ||  ch == ',' || ch == '.'
        ||  ch == '?' || ch == '!' 
        ||  ch == '<' || ch == '>'
        ||  ch == '=');
}

Token Compiler::get_symbol() {
    this->skip_whitespace();

    assert(this->at_symbol());

    string text;
    text += this->script.get_char();

    return Token(text);
}

bool Compiler::at_string_literal() {
    char ch = this->script.get_current_char();

    return (ch == '"');
}

Token Compiler::get_string_literal() {
    this->skip_whitespace();

    char ch = this->script.get_char();
    assert(ch == '"');

    string text = "\"";
    while (!this->at_end() && !this->at_string_literal()) {
        text += this->script.get_char();
    }

    if (!this->at_string_literal()) {
        throw VccException("missing closing quote");
    }

    ch = this->script.get_char();
    assert(ch == '"');

    text += ch;

    return Token(text);
}

void Compiler::expect(std::string token_text) {
    Token token = this->get_token();
    if (token.get_text() != token_text) {
        throw ExpectedTokenException(token_text, token.get_text(), 0);
    }
}

bool Compiler::parse_event() {
    if (this->at_end()) {
        return false;
    }
    this->expect("event");
    this->expect("{");
    this->expect("}");
    return true;
}

bool Compiler::is_library_function(Token token) {
    return token.get_text() == "mapswitch";
}

int Compiler::parse_term() {
    int value = this->parse_factor();

    while (true) {
        Token token = this->look_ahead();
        if (token.get_text() == "*") {
            this->get_token();
            value *= this->parse_factor();
        } else if (token.get_text() == "/") {
            this->get_token();
            value /= this->parse_factor();
        } else {
            break;
        }
    }

    return value;
}

int Compiler::parse_expression() {
    int value = this->parse_term();

    while (true) {
        Token token = this->look_ahead();
        if (token.get_text() == "+") {
            this->get_token();
            value += this->parse_term();
        } else if (token.get_text() == "-") {
            this->get_token();
            value -= this->parse_term();
        } else if (token.get_text() == ")" || token.get_text() == "") {
            break;
        } else {
            throw ExpectedTokenException("+, -, *, or /", token.get_text(), 1);
        }
    }

    return value;
}

Token Compiler::look_ahead() {
    int last_parsing_offset = this->get_parsing_offset();

    Token token = this->get_token();

    this->script.set_offset(last_parsing_offset);

    return token;
}

int Compiler::parse_factor() {
    if (this->look_ahead().get_text() == "(") {
        this->expect("(");
        int value = this->parse_expression();
        this->expect(")");
        return value;
    }

    IntegerToken token = this->get_number();
    return token.get_integer();
}
